import fs from 'fs';
import os from 'os';
import path from 'path';
import readline from 'readline';
import { spawnSync } from 'child_process';
import { createLogger } from './Logger.js';
import { resolveOpsHomeDir } from './OpsFile.js';
import { refreshEnvironment } from '../bin/init.js';

const DEFAULT_CHECK_INTERVAL_MS = 0;

/**
 * CLI 自动更新工具，在每次执行命令前检测仓库是否存在新提交并同步。
 */
export class AutoUpdater {
    /**
     * @param {object} options
     * @param {string} options.repoDir 仓库根目录
     * @param {import('./Logger.js').Logger} [options.logger] 日志实例
     * @param {number} [options.checkIntervalMs] 检查间隔（毫秒）
     */
    constructor({ repoDir, logger, checkIntervalMs = DEFAULT_CHECK_INTERVAL_MS } = {}) {
        this.repoDir = repoDir;
        this.logger = logger || createLogger('AutoUpdate');
        const envInterval = Number(process.env.NIKOU_AUTO_UPDATE_INTERVAL_MS);
        if (!Number.isNaN(envInterval) && Number.isFinite(envInterval) && envInterval >= 0) {
            this.checkIntervalMs = envInterval;
        } else {
            this.checkIntervalMs = checkIntervalMs;
        }
        this.metadataFile = this.resolveMetadataFile();
        this.devMode = this.resolveDevModeFlag();
    }

    resolveMetadataFile() {
        const homeDir = os.homedir();
        const cacheDir = path.join(homeDir, '.ops', 'cache');
        try {
            fs.mkdirSync(cacheDir, { recursive: true });
        } catch (error) {
            this.logger.debug?.(`创建缓存目录失败: ${error.message}`);
        }
        return path.join(cacheDir, 'nikou-cli-auto-update.json');
    }

    /**
     * 执行更新逻辑
     */
    async run() {
        if (this.shouldSkip()) {
            return;
        }
        this.touchTimestamp();
        try {
            await this.updateRepo(this.repoDir, 'CLI 仓库');
        } catch (error) {
            this.logger.warn(`自动更新失败，将继续执行命令: ${error.message}`);
        }
        try {
            await this.updateNikouPluginIfNeeded();
        } catch (error) {
            this.logger.warn(`nikou 插件自动更新失败，将继续执行命令: ${error.message}`);
        }
    }

    /**
     * 更新指定 Git 仓库
     * @param {string} targetDir 仓库目录
     * @param {string} label 日志展示名称
     * @returns {Promise<void>}
     */
    async updateRepo(targetDir, label) {
        if (!targetDir) {
            return;
        }
        const repoPath = path.resolve(targetDir);
        if (!fs.existsSync(repoPath)) {
            return;
        }
        if (!this.isGitRepo(repoPath)) {
            this.logger.debug?.(`${label} 目录不是 Git 仓库，跳过自动更新: ${repoPath}`);
            return;
        }
        if (this.devMode) {
            this.logger.debug?.('检测到 is_dev=true，处于开发者模式，跳过自动更新与强制同步。');
            return;
        }
        const previousHead = this.getRevision('HEAD', false, repoPath);
        const currentBranch = this.getCurrentBranch(repoPath);
        if (!currentBranch) {
            return;
        }
        const status = this.getWorkingTreeStatus(repoPath);
        const hasLocalChanges = status.hasTrackedChanges || status.hasUntrackedChanges;
        if (hasLocalChanges) {
            if (status.hasTrackedChanges) {
                this.logger.warn(`检测到 ${label} 已跟踪文件存在未提交修改，已自动执行强制同步流程。`);
            } else {
                this.logger.warn(`检测到 ${label} 存在未跟踪文件，已自动清理并执行强制同步流程。`);
            }
            this.fetchRemote(repoPath);
            const remoteHead = this.getRevision(`origin/${currentBranch}`, true, repoPath);
            this.logger.info(`正在自动强制同步 ${label} 最新代码...`);
            if (remoteHead) {
                this.forceResetToRemote(currentBranch, status.hasUntrackedChanges, repoPath);
                this.logger.success(`${label} 已同步至远端最新提交 (${remoteHead.slice(0, 7)}).`);
            } else {
                this.logger.warn(`未找到 origin/${currentBranch}，将重置到当前提交。`);
                this.forceResetToHead(status.hasUntrackedChanges, repoPath);
                this.logger.success(`${label} 已重置至本地最新提交。`);
            }
            const newHead = this.getRevision('HEAD', false, repoPath);
            await this.refreshEnvironmentIfNeeded(previousHead, newHead, repoPath);
            return;
        }
        this.fetchRemote(repoPath);
        const remoteHead = this.getRevision(`origin/${currentBranch}`, true, repoPath);
        if (!remoteHead) {
            this.logger.debug?.(`未找到 origin/${currentBranch}，跳过 ${label} 自动更新。`);
            return;
        }
        const localHead = previousHead;
        if (localHead === remoteHead) {
            return;
        }
        this.logger.info(`检测到 ${label} 有更新，正在同步最新代码...`);
        this.pullRemote(currentBranch, repoPath);
        const newHead = this.getRevision('HEAD', false, repoPath);
        await this.refreshEnvironmentIfNeeded(localHead, newHead, repoPath);
        this.logger.success(`${label} 自动更新完成。`);
    }

    /**
     * 若配置了 nikou 插件目录，则尝试同步更新
     */
    async updateNikouPluginIfNeeded() {
        const pluginPath = this.resolveNikouPluginPath();
        if (!pluginPath) {
            return;
        }
        if (!fs.existsSync(pluginPath)) {
            this.logger.warn(`检测到配置的 nikou 插件目录不存在: ${pluginPath}，可重新运行 nb-init 修复配置。`);
            return;
        }
        if (!this.isGitRepo(pluginPath)) {
            this.logger.warn(`配置的 nikou 插件目录不是 Git 仓库: ${pluginPath}，请确认是否通过 git clone 安装。`);
            return;
        }
        await this.updateRepo(pluginPath, 'nikou 插件');
    }

    /**
     * 读取配置文件中的 nikou 插件目录
     * @returns {string|null}
     */
    resolveNikouPluginPath() {
        try {
            const configPath = path.join(resolveOpsHomeDir(), 'ops_global.properties');
            if (!fs.existsSync(configPath)) {
                return null;
            }
            const content = fs.readFileSync(configPath, 'utf-8');
            const lines = content.split('\n');
            for (const line of lines) {
                const trimmed = line.trim();
                if (!trimmed || trimmed.startsWith('#') || trimmed.startsWith('!')) {
                    continue;
                }
                const idx = trimmed.indexOf('=');
                if (idx <= 0) {
                    continue;
                }
                const key = trimmed.slice(0, idx).trim();
                if (key !== 'nikou_plugin_path') {
                    continue;
                }
                const value = trimmed.slice(idx + 1).trim();
                if (!value) {
                    return null;
                }
                return path.resolve(value);
            }
            return null;
        } catch (error) {
            this.logger.debug?.(`读取 nikou 插件配置失败: ${error.message}`);
            return null;
        }
    }

    resolveDevModeFlag() {
        try {
            const configPath = path.join(resolveOpsHomeDir(), 'ops_global.properties');
            if (!fs.existsSync(configPath)) {
                return false;
            }
            const content = fs.readFileSync(configPath, 'utf-8');
            const lines = content.split('\n');
            for (const rawLine of lines) {
                const line = rawLine.trim();
                if (!line || line.startsWith('#') || line.startsWith('!')) {
                    continue;
                }
                const idx = line.indexOf('=');
                if (idx <= 0) {
                    continue;
                }
                const key = line.slice(0, idx).trim();
                if (key !== 'is_dev') {
                    continue;
                }
                const value = line.slice(idx + 1).trim().toLowerCase();
                return value === 'true' || value === '1' || value === 'yes' || value === 'y';
            }
            return false;
        } catch (error) {
            this.logger.debug?.(`读取 is_dev 配置失败: ${error.message}`);
            return false;
        }
    }

    /**
     * 判断是否应跳过检测
     * @returns {boolean}
     */
    shouldSkip() {
        if (!this.repoDir) {
            return true;
        }
        if (process.env.NIKOU_AUTO_UPDATE === 'off' || process.env.NIKOU_AUTO_UPDATE === 'false') {
            return true;
        }
        if (this.checkIntervalMs <= 0) {
            return false;
        }
        const lastCheckTimestamp = this.getLastCheckTimestamp();
        if (!lastCheckTimestamp) {
            return false;
        }
        const diff = Date.now() - lastCheckTimestamp;
        return diff < this.checkIntervalMs;
    }

    /**
     * 记录最新检查时间
     */
    touchTimestamp() {
        if (this.checkIntervalMs <= 0) {
            return;
        }
        try {
            const payload = { lastCheck: Date.now() };
            fs.writeFileSync(this.metadataFile, JSON.stringify(payload));
        } catch (error) {
            this.logger.debug?.(`更新自动检查时间失败: ${error.message}`);
        }
    }

    /**
     * 读取上一次检查时间
     * @returns {number|null}
     */
    getLastCheckTimestamp() {
        try {
            if (!fs.existsSync(this.metadataFile)) {
                return null;
            }
            const raw = fs.readFileSync(this.metadataFile, 'utf-8');
            if (!raw.trim()) {
                return null;
            }
            const parsed = JSON.parse(raw);
            return typeof parsed.lastCheck === 'number' ? parsed.lastCheck : null;
        } catch (error) {
            this.logger.debug?.(`读取自动检查时间失败: ${error.message}`);
            return null;
        }
    }

    getChangedFiles(fromRef, toRef, dir = this.repoDir) {
        if (!fromRef || !toRef || fromRef === toRef) {
            return [];
        }
        const result = spawnSync('git', ['diff', '--name-status', `${fromRef}..${toRef}`], {
            cwd: dir || this.repoDir,
            encoding: 'utf-8'
        });
        if (result.status !== 0) {
            this.logger.debug?.(`获取变更列表失败: ${result.stderr || 'git diff 执行失败'}`);
            return [];
        }
        const output = String(result.stdout || '').trim();
        if (!output) {
            return [];
        }
        return output
            .split('\n')
            .map(line => {
                const [status, ...rest] = line.trim().split(/\s+/);
                return {
                    status,
                    file: rest.join(' ')
                };
            })
            .filter(item => item.status && item.file);
    }

    isScriptPath(filePath) {
        return (
            typeof filePath === 'string' &&
            filePath.endsWith('.js') &&
            (filePath.startsWith('bin/') ||
                filePath.startsWith('commands/') ||
                filePath.startsWith('utils/') ||
                filePath.startsWith('scripts/') ||
                filePath === 'router.js')
        );
    }

    hasScriptAdditions(changes) {
        return changes.some(change => change.status?.startsWith('A') && this.isScriptPath(change.file));
    }

    readPackageJsonAtRef(ref, dir = this.repoDir) {
        if (!ref) {
            return null;
        }
        const result = spawnSync('git', ['show', `${ref}:package.json`], {
            cwd: dir || this.repoDir,
            encoding: 'utf-8'
        });
        if (result.status !== 0) {
            this.logger.debug?.(`读取 ${ref}:package.json 失败: ${result.stderr || '未知错误'}`);
            return null;
        }
        const raw = String(result.stdout || '').trim();
        if (!raw) {
            return null;
        }
        try {
            return JSON.parse(raw);
        } catch (error) {
            this.logger.debug?.(`解析 ${ref}:package.json 失败: ${error.message}`);
            return null;
        }
    }

    compareDependencies(prev = {}, next = {}) {
        const prevKeys = Object.keys(prev || {});
        const nextKeys = Object.keys(next || {});
        if (prevKeys.length !== nextKeys.length) {
            return true;
        }
        const keySet = new Set([...prevKeys, ...nextKeys]);
        for (const name of keySet) {
            const prevVersion = (prev && prev[name]) || null;
            const nextVersion = (next && next[name]) || null;
            if (prevVersion !== nextVersion) {
                return true;
            }
        }
        return false;
    }

    hasDependencyChanges(fromRef, toRef, dir = this.repoDir) {
        if (!fromRef || !toRef || fromRef === toRef) {
            return false;
        }
        const prevPkg = this.readPackageJsonAtRef(fromRef, dir);
        const nextPkg = this.readPackageJsonAtRef(toRef, dir);
        if (!nextPkg) {
            return false;
        }
        if (!prevPkg) {
            return true;
        }
        return (
            this.compareDependencies(prevPkg.dependencies, nextPkg.dependencies) ||
            this.compareDependencies(prevPkg.devDependencies, nextPkg.devDependencies)
        );
    }

    async refreshEnvironmentIfNeeded(fromRef, toRef, repoPath) {
        const changes = this.getChangedFiles(fromRef, toRef, repoPath);
        const hasScriptAdd = this.hasScriptAdditions(changes);
        const hasDepsChange = this.hasDependencyChanges(fromRef, toRef, repoPath);
        if (!hasScriptAdd && !hasDepsChange) {
            this.logger.info('本次更新未新增脚本或依赖，跳过安装与重链。');
            return;
        }
        const reasons = [];
        if (hasScriptAdd) {
            reasons.push('新增脚本');
        }
        if (hasDepsChange) {
            reasons.push('依赖变更');
        }
        this.logger.info(`检测到 ${reasons.join('、')}，正在刷新环境依赖与命令链接...`);
        await refreshEnvironment(repoPath);
    }

    /**
     * 是否处于 Git 仓库内
     * @returns {boolean}
     */
    isGitRepo(dir = this.repoDir) {
        const repoDir = dir || this.repoDir;
        if (!repoDir) {
            return false;
        }
        const result = spawnSync('git', ['rev-parse', '--is-inside-work-tree'], {
            cwd: repoDir,
            encoding: 'utf-8'
        });
        return result.status === 0 && String(result.stdout || '').trim() === 'true';
    }

    /**
     * 是否存在未提交修改
     * @returns {boolean}
     */
    getWorkingTreeStatus(dir = this.repoDir) {
        const result = spawnSync('git', ['status', '--porcelain'], {
            cwd: dir || this.repoDir,
            encoding: 'utf-8'
        });
        if (result.error || result.status !== 0) {
            throw new Error(result.error ? result.error.message : (result.stderr || 'git status error'));
        }
        const output = String(result.stdout || '').trim();
        if (!output) {
            return {
                hasTrackedChanges: false,
                hasUntrackedChanges: false
            };
        }
        const lines = output.split('\n');
        let hasTrackedChanges = false;
        let hasUntrackedChanges = false;
        for (const line of lines) {
            if (!line) continue;
            if (line.startsWith('??')) {
                hasUntrackedChanges = true;
            } else {
                hasTrackedChanges = true;
            }
            if (hasTrackedChanges && hasUntrackedChanges) {
                break;
            }
        }
        return {
            hasTrackedChanges,
            hasUntrackedChanges
        };
    }

    /**
     * 获取当前分支
     * @returns {string|null}
     */
    getCurrentBranch(dir = this.repoDir) {
        const result = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
            cwd: dir || this.repoDir,
            encoding: 'utf-8'
        });
        if (result.status !== 0) {
            throw new Error(result.stderr || '获取当前分支失败');
        }
        const branch = String(result.stdout || '').trim();
        if (!branch || branch === 'HEAD') {
            return null;
        }
        return branch;
    }

    /**
     * 获取指定引用的哈希
     * @param {string} ref 引用
     * @param {boolean} [allowFail=false] 是否允许失败
     * @returns {string|null}
     */
    getRevision(ref, allowFail = false, dir = this.repoDir) {
        const result = spawnSync('git', ['rev-parse', ref], {
            cwd: dir || this.repoDir,
            encoding: 'utf-8'
        });
        if (result.status !== 0) {
            if (allowFail) {
                return null;
            }
            throw new Error(result.stderr || `无法解析引用 ${ref}`);
        }
        return String(result.stdout || '').trim();
    }

    /**
     * 执行 fetch
     */
    fetchRemote(dir = this.repoDir) {
        const result = spawnSync('git', ['fetch', '--all', '--prune'], {
            cwd: dir || this.repoDir,
            encoding: 'utf-8',
            stdio: 'ignore'
        });
        if (result.status !== 0) {
            throw new Error(result.stderr || 'git fetch 失败');
        }
    }

    /**
     * 执行 pull
     * @param {string} branch
     */
    pullRemote(branch, dir = this.repoDir) {
        const result = spawnSync('git', ['pull', '--rebase', 'origin', branch], {
            cwd: dir || this.repoDir,
            encoding: 'utf-8',
            stdio: 'inherit'
        });
        if (result.status !== 0) {
            throw new Error('git pull 失败');
        }
    }

    /**
     * 用户确认后强制同步到远端
     * @param {string} branch 当前分支
     * @param {boolean} cleanUntracked 是否需要清理未跟踪文件
     */
    forceResetToRemote(branch, cleanUntracked, dir = this.repoDir) {
        const resetResult = spawnSync('git', ['reset', '--hard', `origin/${branch}`], {
            cwd: dir || this.repoDir,
            encoding: 'utf-8',
            stdio: 'inherit'
        });
        if (resetResult.status !== 0) {
            throw new Error('git reset --hard 失败');
        }
        if (cleanUntracked) {
            const cleanResult = spawnSync('git', ['clean', '-fd'], {
                cwd: dir || this.repoDir,
                encoding: 'utf-8',
                stdio: 'inherit'
            });
            if (cleanResult.status !== 0) {
                throw new Error('git clean 失败');
            }
        }
    }

    /**
     * 强制重置到当前提交
     * @param {boolean} cleanUntracked 是否需要清理未跟踪文件
     */
    forceResetToHead(cleanUntracked, dir = this.repoDir) {
        const resetResult = spawnSync('git', ['reset', '--hard'], {
            cwd: dir || this.repoDir,
            encoding: 'utf-8',
            stdio: 'inherit'
        });
        if (resetResult.status !== 0) {
            throw new Error('git reset --hard 失败');
        }
        if (cleanUntracked) {
            const cleanResult = spawnSync('git', ['clean', '-fd'], {
                cwd: dir || this.repoDir,
                encoding: 'utf-8',
                stdio: 'inherit'
            });
            if (cleanResult.status !== 0) {
                throw new Error('git clean 失败');
            }
        }
    }

    /**
     * 确认是否强制更新
     * @returns {Promise<boolean>}
     */
    async confirmForceUpdate(label = 'CLI 仓库') {
        if (!process.stdin.isTTY || !process.stdout.isTTY) {
            this.logger.warn(`检测到 ${label} 有更新但当前终端不可交互，已跳过强制更新。`);
            return false;
        }
        const question = `检测到 ${label} 存在未提交更改，是否强制更新并丢弃本地修改？[Y/n] `;
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout
        });
        return await new Promise(resolve => {
            rl.question(question, answer => {
                rl.close();
                const normalized = String(answer || '').trim().toLowerCase();
                resolve(normalized === '' || normalized === 'y');
            });
        });
    }
}
